﻿#region NeoPatterns
// 
// NeoPatterns framework is released under LGPLv3 license.
// See http://www.opensource.org/licenses/lgpl-3.0.html for more details.
// 
// Please visit http://neopatterns.wikidot.com for more information about NeoPatterns
// 
// Copyright (c) Pascal Craponne 2008
// 
#endregion

using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using NeoPatterns.Exception;

namespace NeoPatterns.Core.Aop.Implementation
{
    /// <summary>
    /// Main interception engine.
    /// - stores information related to call, such as target and arguments
    /// - advisors/proxy chain
    /// One interceptor is created by each instance proxied method (the only know way to guarantee thread safety)
    /// </summary>
    internal class Invocation : IInvocation
    {
        /// <summary>
        /// Gets or sets arguments
        /// </summary>
        public object[] Parameters { get; private set; }

        /// <summary>
        /// Optional value returned by called method
        /// </summary>
        public object ReturnValue { get; set; }

        /// <summary>
        /// Call target object
        /// </summary>
        public object Target { get; private set; }

        /// <summary>
        /// Invoked method
        /// </summary>
        public MethodInfo TargetMethod { get; private set; }

        /// <summary>
        /// Advisors chain
        /// </summary>
        public Aspect[] Aspect { get; private set; }

        /// <summary>
        /// Current advisor index (the one being processed)
        /// </summary>
        private int AspectIndex;

        /// <summary>
        /// Method to be called in the end: contains original code
        /// </summary>
        public Action<IInvocation> BaseMethod { get; private set; }

        /// <summary>
        /// Passes invocation to next advisor or target
        /// </summary>
        public void Proceed()
        {
            var aspectIndex = AspectIndex++;
            if (aspectIndex < Aspect.Length)
            {
                var aspect = Aspect[aspectIndex];
                // if the advice applies, use it
                // a null Pointcut means "all crosscuts"
                if (aspect.Pointcut == null || aspect.Pointcut.Select(TargetMethod)) // TODO: see how it works with properties
                    aspect.Advisor.Advise(this);
                else // otherwise, see next
                    Proceed();
            }
            else if (aspectIndex == Aspect.Length)
            {
                if (BaseMethod == null)
                    throw new NullTargetException();
                // this test because some advisor may decide to break the chain (not to call the followers)
                // in this case, the maximal index is never reached and we must not call the final (original) method
                BaseMethod.Invoke(this);
            }
        }

        /// <summary>
        /// Creates invocation
        /// </summary>
        /// <param name="proxy"></param>
        /// <param name="parameters"></param>
        /// <param name="crossCuttings"></param>
        /// <param name="targetMethod"></param>
        /// <param name="baseMethod"></param>
        public Invocation(object proxy, object[] parameters, IEnumerable crossCuttings,
                          MethodInfo targetMethod, Action<IInvocation> baseMethod)
        {
            Target = proxy;
            Parameters = parameters;
            Aspect = crossCuttings.Cast<Aspect>().ToArray();
            TargetMethod = targetMethod;
            BaseMethod = baseMethod;
        }
    }
}